/**
   \file  config.hh
   \brief libgist driver program config file API.

   The libgist driver program needs to know where it should look for its
   input data, where various outputs should be stored, etc. It reads all
   of these parameters from a config file, which is a Python program.
   This file defines a class that encapsulates the nitty-gritty of the
   Python interface and presents a high-level API to the libgist driver
   program's other modules.
*/

/*
   This file is part of libgist.

   libgist is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2 of the License, or (at your
   option) any later version.

   libgist is distributed in the hope that it will be useful, but WITHOUT
   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
   for more details.

   You should have received a copy of the GNU General Public License
   along with libgist; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/

/*
   REVISION HISTORY

   $HeadURL: http://libgist.googlecode.com/svn/branches/siagian_dev/drv/config.hh $
   $Id: config.hh 75 2011-09-29 11:15:19Z libgist@gmail.com $
*/

#ifndef GISTDRV_CONFIGURATION_API_DOT_H
#define GISTDRV_CONFIGURATION_API_DOT_H

//------------------------------ HEADERS --------------------------------

// gistdrv
#include "singleton.hh"
#include "exceptions.hh"
#include "string_utils.hh"
#include "stl_utils.hh"

// libgist
#include "local_fn.hh"

// Boost
#include <boost/python.hpp>
#include <boost/python/stl_iterator.hpp>

// Standard C++
#include <algorithm>
#include <string>
#include <map>
#include <vector>
#include <iterator>
#include <utility>

//----------------------------- NAMESPACE -------------------------------

namespace gistdrv {

//------------------------- CLASS DEFINITION ----------------------------

/**
   \brief An API for retrieving configuration settings specified via a
   Python program.

   libgist provides canned C++ implementations of several different gist
   algorithms for image/scene classification/categorization. Each
   algorithm has its own way of doing things. Therefore, we need a
   flexible method for specifying various settings (e.g., location of
   input data, where to send outputs, etc.).

   The libgist driver program solves this problem by executing a Python
   script to load its settings. The advantage of specifying configuration
   settings using Python instead of a simple INI or similar config file
   is that it allows users to perform any necessary data preprocessing
   before running the requested gist algorithm(s).

   The Python program that specifies various config settings should
   eventually create objects for the different libgist algorithms. The
   data members (or properties) of these config objects will contain the
   desired settings. The libgist driver program's client modules can
   retrieve these settings by supplying suitable object and property
   names to the config retrieval API's provided by this class, which
   encapsulates the details of interfacing C++ and Python so that the
   other modules of the libgist driver can retrieve relevant
   configuration settings using a simple API.
*/
class config: public singleton<config> {
   // Boilerplate code to make singleton pattern work.
   friend class singleton<config> ;

   /// The libgist driver program executes a Python script to load
   /// various configuration settings. This script is expected to create
   /// objects whose data members (or properties) will hold the desired
   /// settings. These Python objects should exist in the Python script's
   /// main namespace. Therefore, to access the values of various
   /// settings, this (C++) configuration object will need a reference to
   /// the Python script's main namespace. This data member holds the
   /// above-mentioned reference to the main namespace.
   boost::python::object m_main_namespace ;

   /// Private constructor because the config object is a singleton.
   config() ;

   /// Clean-up.
   ~config() ;

public:
   /**
      \brief Load specified config file.
      \param config_file Name of configuration file to load.
      \param params Command line parameters to pass to Python script.

      This method loads the configuration settings from the specified
      file. As mentioned earlier, the libgist driver program expects its
      config file to be a Python script. If the specified file does not
      exist, a no_such_file exception will be thrown.

      The full path to the config file plus the strings supplied in the
      params parameter will be passed to the Python script as command
      line arguments. If clients do not supply this parameter, it will
      default to an empty list.
   */
   static void load(const std::string& config_file,
                    const std::vector<std::string>& params =
                          std::vector<std::string>()) ;

   /**
      \brief  Retrieve setting corresponding to specified object property.
      \param  object Name of object containing property.
      \param  property Name of property to retrieve.
      \return Value corresponding to object property as an instance of type T.

      In general, to specify configuration settings for different
      algorithms, users should create appropriately named Python classes
      and objects and member variables. For example, an algorithm foo,
      should be represented in the config file by a class foo_conf and an
      instance of foo_conf named foo. Then, if algorithm foo expects a
      configuration variable bar, the Python class foo_conf should have a
      member variable bar.

      This function retrieves the setting specified by the Python
      object.property. If the specified object and property do not exist
      in the Python program, a python_error will be thrown.

      As this function is a template, the specified object's property's
      value will be converted into the appropriate C++ type (as long as
      Boost.Python supports extraction and conversion to the type T).
   */
   template<typename T>
   static T get(const std::string& object, const std::string& property) ;

   /**
      \brief  Retrieve setting corresponding to specified top-level property.
      \param  property Name of top-level property to retrieve.
      \return Value corresponding to object property as an instance of type T.

      In general, to specify configuration settings for different
      algorithms, users should create appropriately named Python classes
      and objects and member variables. For example, an algorithm foo,
      should be represented in the config file by a class foo_conf and an
      instance of foo_conf named foo. Then, if algorithm foo expects a
      configuration variable bar, the Python class foo_conf should have a
      member variable bar.

      However, some settings need not be attached to objects, i.e., they
      can be top-level/global variables in the Python program. This
      function retrieves the values of such top-level variables. If the
      specified variable has not been defined in the Python program, a
      python_error will be thrown.

      As this function is a template, the specified object's property's
      value will be converted into the appropriate C++ type (as long as
      Boost.Python supports extraction and conversion to the type T).
   */
   template<typename T>
   static T get(const std::string& property) ;

   /**
      \brief  Retrieve object property in an STL vector.
      \param  object Name of object containing property.
      \param  property Name of property to retrieve.
      \return Specified object property in an STL vector of T.

      As mentioned earlier, the libgist driver program expects users to
      specify various settings for different gist algorithms via
      approprpiately named Python objects and their properties. This
      function can be used to retrieve Python lists specified by the
      supplied object and property names.

      The list's values will be returned in an STL vector of type T (as
      long as Boost.Python supports extraction and conversion to the type
      T). If the specified object and property do not exist in the Python
      program, a python_error will be thrown.
   */
   template<typename T>
   static std::vector<T>
   get_vector(const std::string& object, const std::string& property) ;

   /**
      \brief  Retrieve top-level property in an STL map of vectors.
      \param  property Name of property to retrieve.
      \return Value of specified property in an STL map of vectors.

      This function retrieves a Python dictionary of lists specified by
      the supplied top-level property name. The dictionary's values will
      be returned in an STL map of vector of type value and indexed by
      type key (as long as Boost.Python supports extraction and
      conversion to the key and value types).

      If the specified top-level property does not exist in the Python
      program, a python_error will be thrown.

      To get a better idea of what this function does, consider the
      following snippet of Python code:

      \code
          my_dict = {'foo': ['f1', 'f2', 'f3'],
                     'bar': ['b1', 'b2', 'b3', 'b4']}
      \endcode

      my_dict is a dictionary indexed by strings. Each key refers to a
      list of strings. This functio will convert the above dictionary of
      lists of strings indexed by strings into an
      std::map<std::string, std::vector<std::string> >.

      This functionality is useful, for instance, when we want to
      retrieve from the libgist config settings the lists of training
      images for different categories.
   */
   template<typename key, typename value>
   static std::map<key, std::vector<value> >
   get_map_of_lists(const std::string& property) ;

   /**
      \brief  Retrieve top-level property as an STL pair.
      \param  property Name of top-level property to retrieve.
      \param  default_value Return value in case property does not exist.
      \return Specified top-level property as an STL pair.

      This function assumes the top-level property of interest is a list
      of at least two elements and returns the first two elements in an
      STL pair. If the config file does not contain the requested
      property, the given default value will be returned.
   */
   template<typename T1, typename T2>
   static std::pair<T1, T2> get_pair(const std::string& property,
                                     const std::pair<T1, T2>& default_value) ;

private:
   /// \brief Exception indicating missing config file; thrown when user
   /// specifies a bogus config file.
   struct no_such_file: public exception {
      no_such_file(const std::string& file_name) ;
   } ;

   /**
      \brief Exception indicating bad config specification.

      If the user botches a config setting (e.g., specifying only one
      element in a list when we expect at least two), we will let him/her
      know via an instance of this class.
   */
   struct bad_config: public exception {
      bad_config(const std::string& property, const std::string& msg) ;
   } ;

   /**
      \brief Exception indicating error in config file's Python code.

      This class encapsulates all the info available from the Python
      interpreter in case the user's config file contains a Python error.
   */
   struct python_error: public exception {
      python_error() ;
   } ;
} ;

// Retrieve, if possible, config setting value corresponding to specified
// object and property name.
template<typename T>
T config::get(const std::string& object, const std::string& property)
{
   namespace py = boost::python ;
   try
   {
      return py::extract<T>(instance().
                            m_main_namespace[object].attr(property.c_str())) ;
   }
   catch (const py::error_already_set&)
   {
      throw python_error() ;
   }
}

// Return, if possible, top-level variable/property
template<typename T>
T config::get(const std::string& property)
{
   namespace py = boost::python ;
   try
   {
      return py::extract<T>(instance().m_main_namespace[property.c_str()]) ;
   }
   catch (const py::error_already_set&)
   {
      throw python_error() ;
   }
}

// Return Python list via an STL vector
template<typename T>
std::vector<T>
config::
get_vector(const std::string& object, const std::string& property)
{
   namespace py = boost::python ;
   try
   {
      py::list L = py::extract<py::list>(instance().
         m_main_namespace[object].attr(property.c_str())) ;
      std::vector<T> v ;
      v.reserve(py::len(L)) ;
      std::copy(py::stl_input_iterator<T>(L), py::stl_input_iterator<T>(),
                std::back_inserter(v)) ;
      return v ;
   }
   catch (const py::error_already_set&)
   {
      throw python_error() ;
   }
}

// Return Python dictionary of lists via an STL map of vectors
template<typename key, typename value>
std::map<key, std::vector<value> >
config::
get_map_of_lists(const std::string& property)
{
   namespace py = boost::python ;
   try
   {
      // First, get the dictionary from the Python interpreter
      py::dict d =
         py::extract<py::dict>(instance().m_main_namespace[property.c_str()]) ;

      // Then, extract the dictionary's keys
      py::list k = d.keys() ;
      std::vector<key> keys ;
      keys.reserve(py::len(k)) ;
      std::copy(py::stl_input_iterator<key>(k), py::stl_input_iterator<key>(),
                std::back_inserter(keys)) ;

      // Helper class to insert the lists corresponding to the dictionary
      // keys into an STL map of vectors.
      class insert: public gist::unary_fn<void, key> {
         typedef std::map<key, std::vector<value> > map_t ;
         const py::dict& dict; // the source Python dictionary of lists
         map_t& map  ;         // the destination C++ map of vectors
      public:
         insert(const py::dict& d, map_t& m): dict(d), map(m) {}

         // Given a key, this function extracts the Python list from the
         // dictionary, converts it to an STL vector and then inserts
         // this vector into the STL map.
         void operator()(const key& k) const {
            py::list list = py::extract<py::list>(dict[k]) ;
            std::vector<value> v ;
            v.reserve(py::len(list)) ;
            std::copy(py::stl_input_iterator<value>(list),
                      py::stl_input_iterator<value>(), std::back_inserter(v)) ;
            map[k] = v ;
         }
      } ;

      // Finally, create an STL map of vectors corresponding to the
      // Python dictionary of lists.
      std::map<key, std::vector<value> > m ;
      insert insert_vector_corresponding_to_key_into_map(d, m) ;
      std::for_each(keys.begin(), keys.end(),
         gist::local_fn(insert_vector_corresponding_to_key_into_map)) ;
      return m ;
   }
   catch (const py::error_already_set&)
   {
      throw python_error() ;
   }
}

// Retrieve an STL pair from specified top-level Python variable
template<typename T1, typename T2>
std::pair<T1, T2>
config::
get_pair(const std::string& property, const std::pair<T1, T2>& default_value)
{
   namespace py = boost::python ;
   try
   {
      py::list L =
         py::extract<py::list>(instance().m_main_namespace[property.c_str()]) ;
      if (py::len(L) < 2)
         throw bad_config(property, "expected a pair") ;

      T1 t1 = py::extract<T1>(L[0]) ;
      T2 t2 = py::extract<T2>(L[1]) ;
      return std::make_pair(t1, t2) ;
   }
   catch (const py::error_already_set&)
   {
      if (PyErr_ExceptionMatches(PyExc_KeyError))
         return default_value ;
      throw python_error() ;
   }
}

//-----------------------------------------------------------------------

} // end of namespace encapsulating this file's definitions

#endif

/* So things look consistent in everyone's emacs... */
/* Local Variables: */
/* indent-tabs-mode: nil */
/* End: */
